//========================================================================
//
// TiffWriter.cc
//
// This file is licensed under the GPLv2 or later
//
// Copyright (C) 2010, 2012 William Bader <williambader@hotmail.com>
// Copyright (C) 2012 Albert Astals Cid <aacid@kde.org>
// Copyright (C) 2012, 2017 Adrian Johnson <ajohnson@redneon.com>
// Copyright (C) 2012 Pino Toscano <pino@kde.org>
// Copyright (C) 2014 Steven Lee <roc.sky@gmail.com>
//
//========================================================================

#include "TiffWriter.h"

#ifdef ENABLE_LIBTIFF

#    include <cstring>

#    ifdef _WIN32
#        include <io.h>
#    endif

extern "C" {
#    include <tiffio.h>
}

struct TiffWriterPrivate
{
    TIFF *f; // LibTiff file context
    int numRows; // number of rows in the image
    int curRow; // number of rows written
    const char *compressionString; // compression type
    TiffWriter::Format format; // format of image data
};

TiffWriter::~TiffWriter()
{
    delete priv;
}

TiffWriter::TiffWriter(Format formatA)
{
    priv = new TiffWriterPrivate;
    priv->f = nullptr;
    priv->numRows = 0;
    priv->curRow = 0;
    priv->compressionString = nullptr;
    priv->format = formatA;
}

// Set the compression type

void TiffWriter::setCompressionString(const char *compressionStringArg)
{
    priv->compressionString = compressionStringArg;
}

// Write a TIFF file.

bool TiffWriter::init(FILE *openedFile, int width, int height, int hDPI, int vDPI)
{
    unsigned int compression;
    uint16 photometric = 0;
    uint32 rowsperstrip = (uint32)-1;
    int bitspersample;
    uint16 samplesperpixel = 0;
    const struct compression_name_tag
    {
        const char *compressionName; // name of the compression option from the command line
        unsigned int compressionCode; // internal libtiff code
        const char *compressionDescription; // descriptive name
    } compressionList[] = { { "none", COMPRESSION_NONE, "no compression" },
                            { "ccittrle", COMPRESSION_CCITTRLE, "CCITT modified Huffman RLE" },
                            { "ccittfax3", COMPRESSION_CCITTFAX3, "CCITT Group 3 fax encoding" },
                            { "ccittt4", COMPRESSION_CCITT_T4, "CCITT T.4 (TIFF 6 name)" },
                            { "ccittfax4", COMPRESSION_CCITTFAX4, "CCITT Group 4 fax encoding" },
                            { "ccittt6", COMPRESSION_CCITT_T6, "CCITT T.6 (TIFF 6 name)" },
                            { "lzw", COMPRESSION_LZW, "Lempel-Ziv  & Welch" },
                            { "ojpeg", COMPRESSION_OJPEG, "!6.0 JPEG" },
                            { "jpeg", COMPRESSION_JPEG, "%JPEG DCT compression" },
                            { "next", COMPRESSION_NEXT, "NeXT 2-bit RLE" },
                            { "packbits", COMPRESSION_PACKBITS, "Macintosh RLE" },
                            { "ccittrlew", COMPRESSION_CCITTRLEW, "CCITT modified Huffman RLE w/ word alignment" },
                            { "deflate", COMPRESSION_DEFLATE, "Deflate compression" },
                            { "adeflate", COMPRESSION_ADOBE_DEFLATE, "Deflate compression, as recognized by Adobe" },
                            { "dcs", COMPRESSION_DCS, "Kodak DCS encoding" },
                            { "jbig", COMPRESSION_JBIG, "ISO JBIG" },
                            { "jp2000", COMPRESSION_JP2000, "Leadtools JPEG2000" },
                            { nullptr, 0, nullptr } };

    // Initialize

    priv->f = nullptr;
    priv->curRow = 0;

    // Store the number of rows

    priv->numRows = height;

    // Set the compression

    compression = COMPRESSION_NONE;

    if (priv->compressionString == nullptr || strcmp(priv->compressionString, "") == 0) {
        compression = COMPRESSION_NONE;
    } else {
        int i;
        for (i = 0; compressionList[i].compressionName != nullptr; i++) {
            if (strcmp(priv->compressionString, compressionList[i].compressionName) == 0) {
                compression = compressionList[i].compressionCode;
                break;
            }
        }
        if (compressionList[i].compressionName == nullptr) {
            fprintf(stderr, "TiffWriter: Unknown compression type '%.10s', using 'none'.\n", priv->compressionString);
            fprintf(stderr, "Known compression types (the tiff library might not support every type)\n");
            for (i = 0; compressionList[i].compressionName != nullptr; i++) {
                fprintf(stderr, "%10s %s\n", compressionList[i].compressionName, compressionList[i].compressionDescription);
            }
        }
    }

    // Set bits per sample, samples per pixel, and photometric type from format

    bitspersample = (priv->format == MONOCHROME ? 1 : 8);

    switch (priv->format) {
    case MONOCHROME:
    case GRAY:
        samplesperpixel = 1;
        photometric = PHOTOMETRIC_MINISBLACK;
        break;

    case RGB:
        samplesperpixel = 3;
        photometric = PHOTOMETRIC_RGB;
        break;

    case RGBA_PREMULTIPLIED:
        samplesperpixel = 4;
        photometric = PHOTOMETRIC_RGB;
        break;

    case CMYK:
        samplesperpixel = 4;
        photometric = PHOTOMETRIC_SEPARATED;
        break;

    case RGB48:
        samplesperpixel = 3;
        bitspersample = 16;
        photometric = PHOTOMETRIC_RGB;
        break;
    }

    // Open the file

    if (openedFile == nullptr) {
        fprintf(stderr, "TiffWriter: No output file given.\n");
        return false;
    }

#    ifdef _WIN32
    // Convert C Library handle to Win32 Handle
    priv->f = TIFFFdOpen(_get_osfhandle(fileno(openedFile)), "-", "w");
#    else
    priv->f = TIFFFdOpen(fileno(openedFile), "-", "w");
#    endif

    if (!priv->f) {
        return false;
    }

    // Set TIFF tags

    TIFFSetField(priv->f, TIFFTAG_IMAGEWIDTH, width);
    TIFFSetField(priv->f, TIFFTAG_IMAGELENGTH, height);
    TIFFSetField(priv->f, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
    TIFFSetField(priv->f, TIFFTAG_SAMPLESPERPIXEL, samplesperpixel);
    TIFFSetField(priv->f, TIFFTAG_BITSPERSAMPLE, bitspersample);
    TIFFSetField(priv->f, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
    TIFFSetField(priv->f, TIFFTAG_PHOTOMETRIC, photometric);
    TIFFSetField(priv->f, TIFFTAG_COMPRESSION, (uint16)compression);
    TIFFSetField(priv->f, TIFFTAG_ROWSPERSTRIP, TIFFDefaultStripSize(priv->f, rowsperstrip));
    TIFFSetField(priv->f, TIFFTAG_XRESOLUTION, (double)hDPI);
    TIFFSetField(priv->f, TIFFTAG_YRESOLUTION, (double)vDPI);
    TIFFSetField(priv->f, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH);

    if (priv->format == RGBA_PREMULTIPLIED) {
        uint16 extra = EXTRASAMPLE_ASSOCALPHA;
        TIFFSetField(priv->f, TIFFTAG_EXTRASAMPLES, 1, &extra);
    }

    if (priv->format == CMYK) {
        TIFFSetField(priv->f, TIFFTAG_INKSET, INKSET_CMYK);
        TIFFSetField(priv->f, TIFFTAG_NUMBEROFINKS, 4);
    }

    return true;
}

bool TiffWriter::writePointers(unsigned char **rowPointers, int rowCount)
{
    // Write all rows to the file

    for (int row = 0; row < rowCount; row++) {
        if (TIFFWriteScanline(priv->f, rowPointers[row], row, 0) < 0) {
            fprintf(stderr, "TiffWriter: Error writing tiff row %d\n", row);
            return false;
        }
    }

    return true;
}

bool TiffWriter::writeRow(unsigned char **rowData)
{
    // Add a single row

    if (TIFFWriteScanline(priv->f, *rowData, priv->curRow, 0) < 0) {
        fprintf(stderr, "TiffWriter: Error writing tiff row %d\n", priv->curRow);
        return false;
    }

    priv->curRow++;

    return true;
}

bool TiffWriter::close()
{
    // Close the file

    TIFFClose(priv->f);

    return true;
}

#endif
